<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no"/>
  <meta name="renderer" content="webkit"/>
  <meta name="force-rendering" content="webkit"/>
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
  <link rel="icon" type="image/x-icon" href="favicon.png">

  <style>
    .container,.container-fluid{-webkit-box-sizing:border-box;box-sizing:border-box;margin-right:auto;margin-left:auto;padding-right:8px;padding-left:8px}.container-fluid::after,.container::after{display:table;clear:both;content:''}.container{width:96%;max-width:1280px}
    @media (min-width:600px){.container{width:94%}}
    @media (min-width:1024px){.container{width:92%}}
    .card-content{position:relative;padding:16px;font-size:14px;line-height:24px}
    .card-primary-title{display:block;font-size:24px;line-height:36px;opacity:.87}
    .card-primary-subtitle{display:block;font-size:14px;line-height:24px;opacity:.54}
    .typo-display-4,.typo-display-4-opacity{font-weight:300;font-size:112px;letter-spacing:-.04em}.typo-display-4-opacity{opacity:.54}
    .text-center{text-align:center!important}
    .center{display:block!important;margin-right:auto!important;margin-left:auto!important}

  </style>
  <!-- MDUI CSS -->
  <link rel="stylesheet" href="https://unpkg.com/mdui@2.0.3/mdui.css">
  <!-- <link rel="stylesheet" href="https://unpkg.com/mdui@1.0.2/dist/css/mdui.min.css"> -->
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <link rel="stylesheet" href="./font.css">
  <title>TOTP 生成器</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
  <!-- MDUI JavaScript -->
  <script src="https://unpkg.com/mdui@2.0.3/mdui.global.js"></script>
</head>
<body class="container" onload="setKeyfromURL();"></body>
  <mdui-top-app-bar>
    <mdui-top-app-bar-title>TOTP 生成器</mdui-top-app-bar-title>
    <div style="flex-grow: 1"></div>
    <mdui-tooltip content="查看 Github">
        <mdui-button-icon>
          <a href="https://github.com/SkyAerope/TOTP-Generator-Web" target="_blank">
            <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 36 36" enable-background="new 0 0 36 36" xml:space="preserve" style="width: 24px;height:24px;">
              <path fill-rule="evenodd" clip-rule="evenodd" fill="#212121" d="M18,1.4C9,1.4,1.7,8.7,1.7,17.7c0,7.2,4.7,13.3,11.1,15.5c0.8,0.1,1.1-0.4,1.1-0.8c0-0.4,0-1.4,0-2.8c-4.5,1-5.5-2.2-5.5-2.2c-0.7-1.9-1.8-2.4-1.8-2.4c-1.5-1,0.1-1,0.1-1c1.6,0.1,2.5,1.7,2.5,1.7c1.5,2.5,3.8,1.8,4.7,1.4c0.1-1.1,0.6-1.8,1-2.2c-3.6-0.4-7.4-1.8-7.4-8.1c0-1.8,0.6-3.2,1.7-4.4c-0.2-0.4-0.7-2.1,0.2-4.3c0,0,1.4-0.4,4.5,1.7c1.3-0.4,2.7-0.5,4.1-0.5c1.4,0,2.8,0.2,4.1,0.5c3.1-2.1,4.5-1.7,4.5-1.7c0.9,2.2,0.3,3.9,0.2,4.3c1,1.1,1.7,2.6,1.7,4.4c0,6.3-3.8,7.6-7.4,8c0.6,0.5,1.1,1.5,1.1,3c0,2.2,0,3.9,0,4.5c0,0.4,0.3,0.9,1.1,0.8c6.5-2.2,11.1-8.3,11.1-15.5C34.3,8.7,27,1.4,18,1.4z"></path>
            </svg>
          </a>
        </mdui-button-icon>
     </mdui-tooltip>
  </mdui-top-app-bar>

  <mdui-text-field label="输入密钥" icon="vpn_key" id="key" oninput="generateTOTP()" autofocus></mdui-text-field>

<p></p>

  <mdui-card clickable class="center" onclick="copyKey()">
    <div class="card-content">
      <div class="card-primary-title">TOTP</div>
      <div class="card-primary-subtitle">点击此卡片可复制TOTP</div>
    <p id="totp" class="typo-display-4 text-center"></p>
    <p id="remaining-time"></p>
    <mdui-linear-progress id="progress" value="0" max="100"></mdui-linear-progress>
  </div>
  </mdui-card>

<!-- 复制 -->
<script>
function copyKey() {
  var totpElement = document.getElementById('totp');
  if (totpElement) {
    var range = document.createRange();
    range.selectNode(totpElement);
    window.getSelection().removeAllRanges();
    window.getSelection().addRange(range);
    document.execCommand('copy');
    window.getSelection().removeAllRanges();
    mdui.snackbar({message: '已复制', position: 'top',});
  }
}

</script>
  
<script>
	var timerId;

	function generateTOTP() {
		clearTimeout(timerId); // 清除之前的定时器

		var key = document.getElementById('key').value;

		// 将所有小写字母转换为大写字母并去掉空格
		key = key.toUpperCase().replace(/\s/g, "");
		// 将Base32编码的密钥解码为二进制
		key = base32Decode(key);

		// 定义哈希算法
		var digestAlgorithm = 'SHA-1';

		// 获取当前时间戳
		var timeStep = 30;  // 默认时间步长为30秒
		var currTime = Math.floor(Date.now() / 1000);

		// 计算到下一个30秒的剩余时间并减去1秒以确保刚好取到当前的30秒
		var remainingTime = timeStep - (currTime % timeStep) - 1;
		var remainingTimems = timeStep - (Date.now() / 1000 % timeStep) - 1;

		// 计算基于时间的单次密码
		var message = Math.floor(currTime / timeStep).toString(16);
		message = message.padStart(16, '0');
		var hmacHash = hmacSHA1(key, hexToBytes(message));
		var offset = hmacHash[19] & 0xf;
		var otp =
			((hmacHash[offset] & 0x7f) << 24) |
			(hmacHash[offset + 1] << 16) |
			(hmacHash[offset + 2] << 8) |
			hmacHash[offset + 3];
		otp = (otp % 1000000).toString().padStart(6, '0');

		// 更新TOTP密码和剩余时间的显示
		document.getElementById('totp').textContent = otp;
		document.getElementById('remaining-time').textContent = '剩余时间: ' + remainingTime + ' 秒';

		// 更新进度条
		var progress = document.getElementById('progress');
		var progressWidth = (remainingTimems / timeStep) * 100;
		progress.style.display = 'block';
		progress.value = progressWidth;

		// 每0.1秒更新一次
		timerId = setTimeout(generateTOTP, 100);
	}

	// Base32解码
	function base32Decode(input) {
		var base32chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
		var base32lookup = {};
		for (var i = 0; i < base32chars.length; i++) {
			base32lookup[base32chars.charAt(i)] = i;
		}
		
		input = input.replace(/=+$/, '');
		var bits = '';
		var output = [];
		for (var i = 0; i < input.length; i++) {
			var char = input.charAt(i).toUpperCase();
			var val = base32lookup[char];
			bits += ('00000' + val.toString(2)).slice(-5);
		}
		while (bits.length >= 8) {
			output.push(parseInt(bits.substring(0, 8), 2));
			bits = bits.substring(8);
		}
		
		return new Uint8Array(output);
	}

	// HMAC-SHA1哈希
	function hmacSHA1(key, message) {
		var keyBytes = new Uint8Array(key);
		var messageBytes = new Uint8Array(message);
		var hmacKey = (keyBytes.length > 64) ? sha1(keyBytes) : keyBytes;
		if (hmacKey.length < 64) {
			var padding = new Uint8Array(64 - hmacKey.length);
			hmacKey = concatenateUint8Arrays(hmacKey, padding);
		}

		var innerPad = new Uint8Array(64);
		var outerPad = new Uint8Array(64);
		for (var i = 0; i < 64; i++) {
			innerPad[i] = hmacKey[i] ^ 0x36;
			outerPad[i] = hmacKey[i] ^ 0x5c;
		}

		var innerHash = sha1(concatenateUint8Arrays(innerPad, messageBytes));
		var outerHash = sha1(concatenateUint8Arrays(outerPad, innerHash));

		return outerHash;
	}

	// SHA1哈希
	function sha1(message) {
		var sha1Hash = CryptoJS.SHA1(CryptoJS.lib.WordArray.create(message));
		return hexToBytes(sha1Hash.toString());
	}

	// 将十六进制字符串转换为字节数组
	function hexToBytes(hex) {
		var bytes = [];
		for (var i = 0; i < hex.length; i += 2) {
			bytes.push(parseInt(hex.substr(i, 2), 16));
		}
		return bytes;
	}

	// 合并Uint8Array
	function concatenateUint8Arrays(a, b) {
		var result = new Uint8Array(a.length + b.length);
		result.set(a);
		result.set(b, a.length);
		return result;
	}
</script>

<script>
function setKeyfromURL(){
	const searchParams = new URLSearchParams(window.location.search);
	const key = searchParams.get("key");
	console.log(key);
	if (key == null){
		return;
	}
	var keytext = document.getElementById("key");
	keytext.value = key;
	generateTOTP();
}
</script>

</body>
</html>
